fix(workflow-executor): drop dead-branch steps from context (PRD-433)#1623
fix(workflow-executor): drop dead-branch steps from context (PRD-433)#1623hercemer42 wants to merge 9 commits into
Conversation
1 new issue
|
4c3cde3 to
b608f35
Compare
|
[Claude Opus 4.8] — Review resolution on #1623 1. Inline conventions comment (test comment referencing the ticket) — agreed and fixed in b608f35. 2. qlty: "Function with many returns (count = 4): getAvailableRecordRefs" — pushing back, not fixing. The four returns are two guard-style early exits inside a 3. CI failure (Linting & Testing workflow-executor) — root cause was not the review findings: the base branch moved under the PR (#1610 reworked the load-related executor; BelongsTo loads now go through |
|
Coverage Impact Unable to calculate total coverage change because base branch coverage was not found. Modified Files with Diff Coverage (3)
🤖 Increase coverage with AI coding...🚦 See full report on Qlty Cloud » 🛟 Help
|
fa5f268 to
459bd01
Compare
Revising a step forks the run history: the orchestrator stamps the pivot card revised and the downstream branch cancelled, then re-appends live clones. The executor read that history branch-blind, so re-executed steps inherited stale AI context and the record pool offered records loaded by superseded steps (e.g. revising "Load store" proposed loading from the dead branch's owner). previousSteps now mirrors the orchestrator's own live-path filter, and each step carries lineageStepIndexes so executions persisted in the RunStore under a clone's original index resolve to the freshest generation — never to a dead branch, never by stepName (LinkTo loops make names non-unique on the live path). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The base branch's load-related rework routes BelongsTo loads through getSingleRelatedData instead of getRelatedData; the revision repro test asserted the old port call. Also rewords a test comment that referenced the ticket (review feedback). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ationale Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
459bd01 to
584f656
Compare
| // RunStore execution lookup candidates, own stepIndex first then earlier same-step | ||
| // generations descending. Revision clones run under a new stepIndex while their execution | ||
| // data stays keyed under the original one. Absent (legacy producers) means "own index only". | ||
| lineageStepIndexes: z.array(z.number().int().nonnegative()).nonempty().optional(), |
There was a problem hiding this comment.
to remove: we shouldn't pollute executor data with something used only for computing the real data
|
Comment from Alban : Found bug :
|
|
Comments from Enki : I think we should actually copy the data for copied steps. It would make the code simpler for a negligible memory cost - the data you fetch with the resolveLineageExecution method we should fetch & copy the data at the adapter level, so that executors don’t care about steps that are copied or not |
…front Replaces the lineage-walk with the frontend's carry-forward model: a step's record comes from its own RunStore entry, falling back to originalStepIndex only for a revision clone the executor never ran. Own-index-first stops a re-executed step that produced no record (failed / skipped / handled manually) from resurfacing its superseded original's record. Drops lineageStepIndexes from the Step type and the lineage grouping from the mapper. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…n-index-first Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ined Drop frontend cross-references and the contested orchestrator-internal claim about re-executions carrying originalStepIndex; describe only what the executor uses the field for. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…thod Per PR review: resolveStepExecution was static. Since RecordStepExecutor extends BaseStepExecutor, make it a protected instance method and call it via `this.`. Pure helper, no behavior change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A re-executed revision clone that errors persists no execution of its own (load-related only saves on success), so own-index resolution finds nothing and the originalStepIndex fallback resurfaced the superseded original's record. Gate the fallback on a non-error outcome. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

fixes PRD-433
Problem
Revising an earlier workflow step did not discard the steps that originally ran after it. The orchestrator marks them correctly (
revisedon the pivot card,cancelledon the downstream branch, still-valid clones re-appended withoriginalStepIndex), but the executor read the run history branch-blind in two places:toPreviousStepsfiltered only ondone && stepIndex < current→ dead-branch entries polluted the AI context for every step type.getAvailableRecordRefs) was built from the raw local RunStore → records loaded by superseded steps were still offered as sources (revising "Load store" proposed loading from the dead branch's owner instead of reloading the store), while live clones (newstepIndex) couldn't be matched to their stored records.Fix (executor-only)
toPreviousStepsnow mirrors the orchestrator's own live-path filter —!revised && !cancelled— so dead-branch steps never reach the AI context or the record pool.originalStepIndex(already on the wire in the orchestrator's history) is declared inServerStepHistoryand forwarded onto each mappedStep. It is set on a revision clone — a still-valid step the orchestrator re-injects — and points at the step the clone copies, where that step's record lives.BaseStepExecutor.resolveStepExecutionresolves a step's stored execution own-index-first: a step the executor ran has its execution at its ownstepIndex; a clone (which never ran) falls back tooriginalStepIndex. Own-index-first is what stops a re-executed step — which has its own entry — from resurfacing the superseded original's record. Both the record pool and the previous-steps AI summary resolve through it.This mirrors the frontend's
carryForwardExecutorDataForCopiedSteps(which copies executor data fromoriginalStepIndexto a clone's new index): the executor just resolves the record on read instead of copying it.Why not
stepName: LinkTo loops are a product feature — the same step name can legitimately appear twice on the live path — so the step index is the only safe key.Tests
Full package suite green (925/925); the changed behavior is covered:
previousStepsand the record pooloriginalStepIndex, while a re-executed step uses its own execution — never the superseded original's (the reported bug: a revised load that yields no record shows no stale record downstream)stepNamekeep their own records🤖 Generated with Claude Code
Note
Drop dead-branch steps from workflow executor context on revision
toPreviousStepsin run-to-available-step-mapper.ts now filters outrevisedandcancelledhistory entries so only live-path steps are surfaced to executors.toLineageStepIndexesutility computes each step's revision lineage (freshest-first), stored aslineageStepIndexeson theSteptype.BaseStepExecutor.resolveLineageExecutionin base-step-executor.ts useslineageStepIndexesto find the most recent valid execution for a step when building previous-step summaries.RecordStepExecutor.getAvailableRecordRefsin record-step-executor.ts now sources related records from livepreviousStepsonly, resolved via lineage, instead of scanning all RunStore executions.Changes since #1623 opened
resolveLineageExecutionwithresolveStepExecutioninBaseStepExecutor.buildPreviousStepsMessagesandBaseStepExecutor.resolveStepExecutionstatic utility, and inRecordStepExecutor.getAvailableRecordRefs, changing execution resolution from iterating multiple lineage generation candidates to finding by the step's ownstepIndexfirst, then falling back tooriginalStepIndexwhen present, ensuring re-executed steps never resurface superseded originals while clones inherit execution data from their copied step [03e2b08]lineageStepIndexesproperty and addedoriginalStepIndexproperty tovalidated.execution.StepSchemain theworkflow-executorpackage, whereoriginalStepIndexis an optional number that points to the step a clone copies, with updated documentation describing clone inheritance semantics and warnings against keying onstepName[03e2b08]tryMapStepsignature inrun-to-available-step-mapperto removelineageStepIndexesparameter and conditionally includeoriginalStepIndexin returnedStepobjects when present on server step history, removedtoLineageStepIndexesfunction that computed lineage arrays, and updatedtoPreviousStepsto call the modifiedtryMapStepwithout passing lineage data [03e2b08]run-to-available-step-mapper.test.ts,base-step-executor.test.ts,load-related-record-step-executor.test.ts,read-record-step-executor.test.ts,trigger-record-action-step-executor.test.ts, andupdate-record-step-executor.test.tsto assert presence or absence oforiginalStepIndexinstead oflineageStepIndexes, verify own-index-first resolution behavior, confirm clones inherit from originals, and ensure re-executed steps prefer their own execution [03e2b08]RecordStepExecutor.getAvailableRecordRefsmethod describing load-related step record resolution logic [e17962e]originalStepIndexfield acrossServerStepHistoryinterface,BaseStepExecutor.resolveStepExecutionmethod, andStepSchemaconstant [76f58fb]BaseStepExecutor.resolveStepExecutionfrom a static method to an instance method in theworkflow-executorpackage [f2d6780]BaseStepExecutor.resolveStepExecutionto prevent re-executed clone steps with error status from inheriting execution data from their original step [7979556]read-record-step-executor.testverifying that errored clone steps do not inherit execution data from their original step [7979556]BaseStepExecutor.resolveStepExecutionmethod [7273844]Macroscope summarized 584f656.